package com.sparrow.common.reload;

import com.sparrow.common.mergeupdate.MergeUpdateBox;
import com.sparrow.common.mergeupdate.impl.DebounceMergeBox;
import com.sparrow.common.mergeupdate.impl.SimpleDelayMergerBox;
import com.sparrow.common.thread.IdExecutorGroup;
import com.sparrow.common.thread.factory.DaemonThreadFactory;
import com.sparrow.common.thread.runnable.NamedRunnable;

import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 资源热更重载
 */
public class ReloadMonitor implements Runnable {

    private WatchService watchService;
    private Map<WatchKey, Path> keys = new ConcurrentHashMap<>();
    private volatile boolean stop;
    private static ExecutorService executor;
    private static Map<Path, ReloadListener> listenerMap;
    private static final ReloadMonitor INSTANCE = new ReloadMonitor();
    /**
     * 防止抖动
     */
    private SimpleDelayMergerBox delayMergerBox = new SimpleDelayMergerBox();
    /**
     * 批量更新的路径
     */
    private ConcurrentHashMap<Path, Boolean> batchPaths = new ConcurrentHashMap<>();


    public ReloadMonitor() {
        try {
            watchService = FileSystems.getDefault().newWatchService();
            this.stop = false;
            this.executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("reload-monitor"));
            listenerMap = new ConcurrentHashMap<>();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        executor.execute(this::run);

        // 注册 JVM 关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                this.destroy();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }));
    }

    public static ReloadMonitor getInstance() {
        return INSTANCE;
    }

    /**
     * 递归注册path路径
     * @param path
     */
    private synchronized void registerDir(Path path) {
        try {
            if (keys.containsValue(path)) {
                return;
            }

            WatchKey key = path.register(watchService,
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_DELETE,
                    StandardWatchEventKinds.ENTRY_MODIFY);
            keys.put(key, path);

            if (Files.isDirectory(path)) {
                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
                    for (Path child : stream) {
                        if (Files.isDirectory(child)) {
                            registerDir(child);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void run() {
        // 无限循环，等待事件发生
        while (!stop) {
            WatchKey key;
            try {
                key = watchService.take(); // 如果 watchService 被关闭，这里会抛异常
            } catch (ClosedWatchServiceException e) {
                // watchService 被关闭，说明是主动退出，不算错误，正常退出线程
                break;
            } catch (InterruptedException e) {
                // 线程中断，也正常退出
                Thread.currentThread().interrupt(); // 保留中断状态
                break;
            }


            Path path = keys.get(key);
            if (path == null) {
                continue;
            }
            List<WatchEvent<?>> events = key.pollEvents();
            for (WatchEvent<?> event : events) {
                WatchEvent.Kind<?> kind = event.kind();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    continue;
                }

                WatchEvent<Path> ev = (WatchEvent<Path>) event;
                Path filename = ev.context();
                Path targetPath = path.resolve(filename);
                synchronized (this) {
                    //缓存成批量操作
                    batchPaths.put(targetPath, Boolean.TRUE);
                }

                if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
                    if (Files.isDirectory(targetPath)) {
                        registerDir(targetPath);
                    }
                }
                //这里延迟下 可以合并多个ENTRY_MODIFY
                //防抖设计 修改一个文件可能会连续触发事件
                delayMergerBox.tryMergeSchedule(new NamedRunnable("ReloadMerge", () -> {
                    runBatchReload();
                }), 1000, TimeUnit.MILLISECONDS);
            }
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);
            }
        }
    }

    private void runBatchReload() {
        List<Path> paths;
        synchronized (this) {
            paths = batchPaths.keySet().stream().collect(Collectors.toList());
            batchPaths.clear();
        }
        for (Path path : paths) {
            callReloadListener(path);
        }
    }

    private static void callReloadListener(Path path) {
        ReloadListener listener = listenerMap.get(path);
        if (listener != null) {
            listener.onReload(path);
        }
    }

    /**
     * 注册Reload path
     * @param path
     * @param reloadListener
     */
    public synchronized void registerReloadPath(Path path, ReloadListener reloadListener) {
        listenerMap.put(path, reloadListener);
        registerDir(path.getParent());
    }

    public void destroy() throws Exception {
        stop = true;
        if (executor != null) {
            executor.shutdownNow();
        }
        if (watchService != null) {
            watchService.close();
        }
    }
}
